---
title: 算法设计
---

## 分治法

分治法的设计思想是 <RedSpan>将一个难以直接解决的大问题分解成一些规模较小的相同问题，以便各个击破，分而治之</RedSpan>。如果规模为 n 的问题可分解为 k 个子问题， 1< k ≤ n，这些子问题互相独立且与原问题相同。分治法产生的子问题往往是原问题的较小模式，这就为递归技术提供了方便。

一般来说，分治算法在<RedSpan>每一层递归上都有 3 个步骤</RedSpan>。

1. **分解**：将原问题分解成一系列的子问题。
2. **求解**：递归地求解各子问题。若子问题足够小则直接求解。
3. **合并**：将子问题的解合并成原问题的解。

凡是涉及到<RedSpan>分组解决的都是分治法</RedSpan>，例如归并排序算法完全依照上述分治算法的 3 个步骤进行。

1. 分解：将 n 个元素分成各含  n/2 个元素的子序列。
2. 求解：用归并排序对两个子序列递归地排序。
3. 合并：合并两个已经排好序的子序列以得到结果。
## 动态规划法
动态规划法与分治法类似，其<RedSpan>基本思想也是将待求解问题分解成若干个子问题，先求解子问题，然后从这些子问题的解得到原问题的解</RedSpan>。与分治法不同的是，适合用动态规划法求解的问题，<RedSpan>经分解得到的子问题往往不是独立的</RedSpan>。若用分治法来解决这类问题，则相同的子问题会被求解多次，以至于最后解决原问题需要耗费指数级时间。

然而，不同子问题的数目常常只有<RedSpan>多项式量级</RedSpan>。如果 <RedSpan>能够保存已解决的子问题的答案，在需要时再找出已求得的答案，</RedSpan>这样就可以避免大量的重复计算，从而得到多项式时间的算法。为了达到这个目的，可以用<RedSpan>一个表来记录所有已解决的子问题的答案。</RedSpan>不管该子问题是否被用到，<RedSpan>只要它被计算过，就将其结果填入表中。</RedSpan>这就是动态规划法的基本思路。

动态规划算法<RedSpan>通常用于求解具有某种最优性质的问题</RedSpan>。在这类问题中，可能会有许多可行解，每个解都对应于一个值，我们希望找到<RedSpan>具有最优值（最大值或最小值）的那个解</RedSpan>。当然，最优解可能会有多个，动态规划算法能找出其中的一个最优解。设计一个动态规划算法，通常按照以下几个步骤进行。

1. <RedSpan>找出最优解的性质</RedSpan>，并刻画其结构特征。
2. <RedSpan>递归的定义最优解的值</RedSpan>
3. 以 <RedSpan>自底向上的方式计算出最优值</RedSpan>
4. 根据计算最优值时得到的信息，<RedSpan>构造出一个最优解</RedSpan>

步骤 1~3 是动态规划算法的基本步骤。在只需要求出最优值的情形下，步骤 4 可以省略。若需要求出问题的一个最优解，则必须执行步骤 4

对于一个给定的问题，若其<RedSpan>具有以下两个性质</RedSpan>，可以考虑用动态规划算法来求解。

1. <RedSpan>最优子结构：</RedSpan>如果<RedSpan>一个问题的最优解中包含了其子问题的最优</RedSpan>，也就是说该问题具有最优子结构。当一个问题具有最优子结构时，提示我们动态规划算法可能会适用，<RedSpan>但是此时贪心策略可能也是适用的。</RedSpan>
2. <RedSpan>重叠子问题（不用重复计算）</RedSpan>：重叠子问题是指<RedSpan>用来求解原问题的递归算法可反复地求解同样的子问题，而不是总在产生新的子问题</RedSpan>。即当一个递归算法不断地调用同一个问题时，就说明该问题包含重叠子问题。



:::tip
动态规划法用于求解全局最优解，分治法不用求最优
:::

### 典型应用 0-1 背包问题
有 n 个物品，第 i 个物品价值为 $v_i$，重量为 $w_i$，其中 $v_i$ 和 $w_i$ 均为非负数，背包的容量为 $W$，$W$ 为非负数。现考虑如何选择装入背包的物品，使装入背包的物品总价值最大。

满足约束条件的任一集合（$x_1$,$x_2$,...,$x_n$） 是问题的一个可行解，问题的目标是要求问题的一个最优解。考虑一个实例，假设 n=5，$W=17$，每个物品的价值和重量如表所示，可将物品 1、2 和 5 装入背包，背包未满，获得价值 22，此时问题解为 （1,1,0,0,1）;也可以将物品 4 和 5 装入背包，背包装满，获得价值 24 ，此时解为 (0,0,0,1,1)。

<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241015233905.png"/>

#### 1. 刻画 0-1 背包问题的最优解的结构。
可以将背包问题的求解过程看作是进行一系列决策的过程，即<RedSpan>决定那些物品应该放入背包，那些物品不放入背包。</RedSpan>如果一个问题的<RedSpan>最优解包含了物品 n</RedSpan>，即 $x_n=1$，那么其余 <RedSpan>$x_1$,$x_2$,...,$x_{n-1}$ 一定构成子问题 1,2,...,n-1 在容量 $W-w_n$ 时的最优解 </RedSpan>。如果这个 <RedSpan>最优解不包含物品 n</RedSpan>，即 $x_n=0$,那么 <RedSpan>$x_1$,$x_2$,...,$x_{n-1}$ 一定构成子问题 1,2,...,n-1 在容量为 W 时的最优解。</RedSpan>
#### 2. 递归求解最优解的值

根据上述分析的最优解的结构递归地定义问题的最优解。设 <RedSpan>`c[i,w]` 表示背包容量为 w 时 i 个物品导致的最优解的总价值，</RedSpan>得到下式。显然，问题要求 `c[n,W]`

$$
c[i,w] =
\begin{cases}
0\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,\,,i=0 \quad or \quad w=0\\
c[i-1,w],\,\,w_i > W\\
max\{c[i-1,w-w_i]+v_i,c[i-1,w]\},\,\, i>0 \quad and \quad w_i \leq W\\
\end{cases}
$$

:::tip
在判断第 i 个物品能不能加入背包时，默认前 i-1 个已经达到了最优解。
:::

**使用动态规划法求解0-1背包问题的时间复杂度是 $O(nW)$**

## 贪心法
和动态规划法一样，贪心法也<RedSpan>经常用于解决最优化问题</RedSpan>。与动态规划法不同的是，贪心法在解决问题的策略上是<RedSpan>仅根据当前已有的信息做出选择，而且一旦做出了选择，不管将来有什么结果，这个选择都不会改变。</RedSpan>换而言之，贪心法<RedSpan>并不是考虑整体最优，它所做出的选择只是在某种意义上的局部最优。</RedSpan>这种局部最优选择<RedSpan>并不能保证总能获得全局最优解，但通常能得到较好的近似最优解。</RedSpan>

:::tip
动态规划法从全局的角度出发，一定可以得到一个最优解；贪心法只考虑当前，只考虑局部的，可能会得出最优，也可能得不到。
:::

贪心法问题一般具有两个重要的性质。

1. <RedSpan>最优子结构</RedSpan>。当一个问题的最优解包含其子问题的最优解时，称此问题具有最优子结构。问题具有最优子结构是可以采用动态规划法或者贪心法求解的关键性质。
2. <RedSpan>贪心选择性质</RedSpan>。指问题的整体最优解可以通过一系列局部最优的选择，即贪心选择来得到。这是贪心法和动态规划法的主要区别。证明一个问题具有贪心选择性质也是贪心法的一个难点。

### 典型应用：背包问题
背包问题的定义与 0-1 背包问题类似，但是 <RedSpan>每个物品可以部分装入背包</RedSpan>，即在 0-1 背包问题中， $x_i=0$ 或者 $x_i=1$；而在背包问题中 ，$0\leq x_i \leq 1$

为了更好的分析此问题，考虑一个例子：n = 5，W = 100，下表给出了各个物品的重量、价值和单位重量的价值。假设物品已经按照其单位重量的价值从大到小排好序。
<img src="https://wkq-img.oss-cn-chengdu.aliyuncs.com/20241016002451.png"/>

为了得到最优解，<RedSpan>必须把背包放满</RedSpan>。现在适用贪心策略求解，首先要选出<RedSpan>度量的标准</RedSpan>。

1. 按<RedSpan>最大价值</RedSpan>先放背包的原则
2. 按<RedSpan>最小重量</RedSpan>先放背包的原则
3. 按<RedSpan>最大单位重量价值</RedSpan>先放背包的原则

## 回溯法
有 <RedSpan>“通用的解题法”</RedSpan>之称，可以<RedSpan>系统地搜索一个问题的所有解或任一解</RedSpan>。在包含问题的所有解的解空间中，按照<RedSpan>深度优先的策略</RedSpan>，从根节点出发搜索解空间树。搜索至任一结点时，总是 <RedSpan>先判断该节点是否肯定不包含问题的解</RedSpan>，如果不包含，则跳过对以该节点为根的子树的搜索，<RedSpan>逐层向其祖先节点回溯；</RedSpan>否则，进入该子树，继续按深度优先的策略进行搜索。

可以理解为先进行深度优先搜索，一直向下探测，当此路不通时，返回上一层探索另外的分支，重复此步骤，这就是回溯，意为先一直探测，当不成功时再返回上一层。

一般用于**解决迷宫类的问题**
